package com.denlaku.longan.util;

import com.denlaku.longan.AppException;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author tianx
 */
@Slf4j
public class ParallelUtil {
    public static <T> List<T> supplyList(List<Supplier<T>> suppliers) {
        return supplyList(suppliers, false);
    }

    public static <T> List<T> supplyList(List<Supplier<T>> suppliers, boolean throwException) {
        if (suppliers.isEmpty()) {
            return Lists.empty();
        }
        return suppliers.parallelStream().map(supplier -> invokeSupplier(supplier, throwException))
            .filter(Objects::nonNull).collect(Collectors.toList());
    }

    private static <T> T invokeSupplier(Supplier<T> supplier, boolean throwException) {
        try {
            return supplier.get();
        } catch (Exception e) {
            log.error("Failed to invoke supplier", e);
            if (throwException) {
                throw new AppException(e);
            }
            return null;
        }
    }

    public static <T> Map<String, T> supplyMap(Map<String, Supplier<T>> supplierMap) {
        return supplyMap(supplierMap, false);
    }

    public static <T> Map<String, T> supplyMap(Map<String, Supplier<T>> supplierMap, boolean throwException) {
        List<Map.Entry<String, Supplier<T>>> entries = Lists.of(supplierMap.entrySet());
        if (entries.isEmpty()) {
            return Maps.empty();
        }
        Map<String, Optional<T>> optionalMap = entries.parallelStream()
            .collect(Collectors.toMap(Map.Entry::getKey, entry -> {
                T t = invokeSupplier(entry.getValue(), throwException);
                return Optional.ofNullable(t);
            }));
        Map<String, T> result = Maps.ofSize(optionalMap.size());
        optionalMap.forEach((key, optional) -> optional.ifPresent(value -> result.put(key, value)));
        return result;
    }

    public static void execute(List<Runnable> tasks) {
        execute(tasks, false);
    }

    public static void execute(List<Runnable> tasks, boolean throwException) {
        if (tasks.isEmpty()) {
            return;
        }
        tasks.parallelStream().forEach(applier -> invokeApplier(applier, throwException));
    }

    private static void invokeApplier(Runnable task, boolean throwException) {
        try {
            task.run();
        } catch (Exception e) {
            log.error("Failed to invoke applier", e);
            if (throwException) {
                throw new AppException(e);
            }
        }
    }
}
